package com.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

/**
 * 无论是客户端还是服务器端基本都分为三步走
 * 1.编写处理器：里面是服务器的处理逻辑，编写处理的时候需要注意接收的是那种类型的Msg
 * 2.编写一个初始化器：比如一些编码解码器，自定义处理器等等
 * 3.编写服务端：服务端需要两个EventLoopGroup，一个是boss，一个worker，还需要一个ServerBootStrap用来设定server启动相关，
 * 因为是非阻塞基于Channel的因此还需要一个ChannelFuture并绑定端口。
 * 在代码开始之前先简单的了解一下EventLoopGroup的执行原理，每个EventLoopGroup都包含了一组的EventLoop，
 * 然后EventLoop是用来管理Channel的，Channel都要注册到（绑定到）EventLoop上。
 * 在我们平时用的阻塞式的IO是不能进行注册的。
 *
 *
 * 为什么需要两个EventLoopGroup来完成NIO呢？
 * 很好理解，一个EventLoopGroup无法同时进行大量的请求连接和响应工作。
 * bossGroup主要是处理连接请求，而workerGroup主要是处理响应
 */
@Component
@Slf4j
public class MyServer {

    // 用来accept客户端连接
    private EventLoopGroup bossGroup = new NioEventLoopGroup();
    // 处理客户端数据的读写操作
    private EventLoopGroup workerGroup = new NioEventLoopGroup();


    @Value("${netty.port}")
    private int port;


    @PostConstruct
    public void start() throws InterruptedException{
        log.info("netty Tcp 服务器启动");
//        // 用来accept客户端连接
//        EventLoopGroup bossGroup = new NioEventLoopGroup();
//        // 处理客户端数据的读写操作
//        EventLoopGroup workerGroup = new NioEventLoopGroup();

            // 启动服务端的类 ServerBootstrap 是启动服务的辅助类，有关socket的参数可以通过ServerBootstrap进行设置。
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //组装NioEventLoopGroup
            serverBootstrap.group(bossGroup, workerGroup)
                    // 设置channel类型为NIO类型
                    .channel(NioServerSocketChannel.class)
                    //使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    // 设置连接配置参数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 日志处理 info级别
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 添加自定义的初始化器，配置入站、出站事件handler
                    .childHandler(new MyServerInitializer());
            // 端口绑定
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            //该方法进行阻塞,等待服务端链路关闭之后继续执行。
            //这种模式一般都是使用Netty模块主动向服务端发送请求，然后最后结束才使用
//            channelFuture.channel().closeFuture().sync();

        if(channelFuture.isSuccess()){
            log.info("启动 Netty Server");
        }
    }


    @PreDestroy
    public void destory() throws InterruptedException {
        bossGroup.shutdownGracefully().sync();
        workerGroup.shutdownGracefully().sync();
        log.info("关闭Netty");
    }

//    public static void main(String[] args) {
//        MyServer server = new MyServer();
//        server.start();
//    }

}
